package com.google.code.guava.reflect.predicate;

import static com.google.code.guava.reflect.predicate.MemberQualifiersPredicate.newMethodModifiersPredicate;
import static com.google.code.guava.reflect.predicate.Qualifier.STATIC;
import static com.google.code.guava.reflect.predicate.Qualifier.concat;

import java.lang.reflect.Method;

import com.google.code.guava.base.BasePredicate;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;

public final class MethodPredicates{

    static class DeclaringClassPredicate extends BasePredicate<Method>{

        private final Predicate<Class<?>> classPredicate;

        public DeclaringClassPredicate(final Predicate<Class<?>> classPredicate){
            this.classPredicate = classPredicate;
        }

        public boolean apply(final Method method){
            return classPredicate.apply(method.getDeclaringClass());
        }

        @Override
        protected boolean equalsPredicate(final Predicate<?> other){
            return other instanceof DeclaringClassPredicate
                   && Objects.equal(classPredicate,
                       ((DeclaringClassPredicate) other).classPredicate);
        }

        @Override
        public int hashCode(){
            return Objects.hashCode(classPredicate);
        }

        @Override
        public String toString(){
            return Objects.toStringHelper(this)
                .add("class predicate", classPredicate)
                .toString();
        }

    }

    static class ThrowingPredicate extends BasePredicate<Method>{

        private final Class<? extends Exception> exceptionType;

        public ThrowingPredicate(final Class<? extends Exception> exceptionType){
            this.exceptionType = exceptionType;
        }

        public boolean apply(final Method input){
            final Class<?>[] exceptionTypes = input.getExceptionTypes();
            for(final Class<?> declaredException : exceptionTypes){
                if(exceptionType.isAssignableFrom(declaredException))
                    return true;
            }
            return false;
        }

        @Override
        protected boolean equalsPredicate(final Predicate<?> other){
            return other instanceof ThrowingPredicate && Objects.equal(

            exceptionType, ((ThrowingPredicate) other).exceptionType);
        }

        @Override
        public int hashCode(){
            return Objects.hashCode(exceptionType);
        }

        @Override
        public String toString(){
            return Objects.toStringHelper(this)
                //
                .add("expected exception type:", exceptionType)
                .toString();
        }

    }

    public static Predicate<Method> forDeclaringClass(
        final Predicate<Class<?>> classPredicate){
        return new DeclaringClassPredicate(classPredicate);
    }

    public static Predicate<Method> instanceMethods(){
        return withoutModifiers(STATIC);
    }

    public static Predicate<Method> staticMethods(){
        return withAnyModifiers(STATIC);
    }

    public static Predicate<Method> throwing(
        final Class<? extends Exception> exceptionType){
        return new ThrowingPredicate(exceptionType);
    }

    public static Predicate<Method> withAllModifiers(
        final Qualifier modifier, final Qualifier ... modifiers){

        return newMethodModifiersPredicate(concat(modifier, modifiers),
            QualifierEvalutionStrategy.CONTAINS_ALL);

    }

    public static Predicate<Method> withAnyModifiers(
        final Qualifier modifier, final Qualifier ... modifiers){

        return newMethodModifiersPredicate(concat(modifier, modifiers),
            QualifierEvalutionStrategy.CONTAINS_ANY);

    }

    public static Predicate<Method> withoutModifiers(
        final Qualifier modifier, final Qualifier ... modifiers){
        return newMethodModifiersPredicate(concat(modifier, modifiers),
            QualifierEvalutionStrategy.CONTAINS_NONE);
    }
}
